//-----------------------------------------------------------------------------
// File: D3DEnumeration.cs
//
// Desc: Enumerates D3D adapters, devices, modes, etc.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
using System;
using System.Collections;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Cerebrum.Direct3D.Forms;
using Cerebrum.Direct3D.Drawing;



namespace Cerebrum.Direct3D.Forms
{

	/// <summary>
	/// Enumeration of all possible D3D vertex processing types
	/// </summary>
	public enum VertexProcessingType
	{
		Software,
		Mixed,
		Hardware,
		PureHardware
	}

}

namespace Cerebrum.Direct3D.Forms.Internal
{


	/// <summary>
	/// Info about a display adapter
	/// </summary>
	public class GraphicsAdapterInfo
	{
		public int AdapterOrdinal;
		public AdapterDetails AdapterDetails;
		public ArrayList DisplayModeList = new ArrayList(); // List of D3DDISPLAYMODEs
		public ArrayList DeviceInfoList = new ArrayList(); // List of D3DDeviceInfos
		public override string ToString() { return AdapterDetails.Description; }
	}




	/// <summary>
	/// Info about a D3D device, including a list of DeviceCombos (see below) 
	/// that work with the device
	/// </summary>
	public class GraphicsDeviceInfo
	{
		public int AdapterOrdinal;
		public DeviceType DevType;
		public Caps Caps;
		public ArrayList DeviceComboList = new ArrayList(); // List of D3DDeviceCombos
		public override string ToString() { return DevType.ToString(); }
	}




	/// <summary>
	/// Info about a depth/stencil buffer format that is incompatible with a
	/// multisample type
	/// </summary>
	public class DepthStencilMultiSampleConflict
	{
		public DepthFormat DepthStencilFormat;
		public MultiSampleType MultiSampleType;
	}




	/// <summary>
	/// A combination of adapter format, back buffer format, and windowed/fullscreen 
	/// that is compatible with a particular D3D device (and the app)
	/// </summary>
	public class DeviceCombo
	{
		public int AdapterOrdinal;
		public DeviceType DevType;
		public Format AdapterFormat;
		public Format BackBufferFormat;
		public bool IsWindowed;
		public ArrayList DepthStencilFormatList = new ArrayList(); // List of D3DFORMATs
		public ArrayList MultiSampleTypeList = new ArrayList(); // List of D3DMULTISAMPLE_TYPEs
		public ArrayList MultiSampleQualityList = new ArrayList(); // List of ints (maxQuality per multisample type)
		public ArrayList DepthStencilMultiSampleConflictList = new ArrayList(); // List of DepthStencilMultiSampleConflicts
		public ArrayList VertexProcessingTypeList = new ArrayList(); // List of VertexProcessingTypes
		public ArrayList PresentIntervalList = new ArrayList(); // List of D3DPRESENT_INTERVALs
	}




	/// <summary>
	/// Used to sort Displaymodes
	/// </summary>
	class DisplayModeComparer : System.Collections.IComparer
	{
		/// <summary>
		/// Compare two display modes
		/// </summary>
		public int Compare(object x, object y)
		{
			DisplayMode dx = (DisplayMode)x;
			DisplayMode dy = (DisplayMode)y;

			if (dx.Width > dy.Width)
				return 1;
			if (dx.Width < dy.Width)
				return -1;
			if (dx.Height > dy.Height)
				return 1;
			if (dx.Height < dy.Height)
				return -1;
			if (dx.Format > dy.Format)
				return 1;
			if (dx.Format < dy.Format)
				return -1;
			if (dx.RefreshRate > dy.RefreshRate)
				return 1;
			if (dx.RefreshRate < dy.RefreshRate)
				return -1;
			return 0;
		}
	}




	/// <summary>
	/// Enumerates available adapters, devices, modes, etc.
	/// </summary>
	public class D3DEnumeration
	{
		/// <summary>
		/// The confirm device delegate which is used to determine if a device 
		/// meets the needs of the simulation
		/// </summary>
		public delegate bool ConfirmDeviceCallbackType(Caps caps, 
			VertexProcessingType vertexProcessingType, Format adapterFormat, 
			Format backBufferFormat);

		public ConfirmDeviceCallbackType ConfirmDeviceCallback;
		public ArrayList AdapterInfoList = new ArrayList(); // List of D3DAdapterInfos

		// The following variables can be used to limit what modes, formats, 
		// etc. are enumerated.  Set them to the values you want before calling
		// Enumerate().
		public int AppMinFullscreenWidth = 640;
		public int AppMinFullscreenHeight = 480;
		public int AppMinColorChannelBits = 5; // min color bits per channel in adapter format
		public int AppMinAlphaChannelBits = 0; // min alpha bits per pixel in back buffer format
		public int AppMinDepthBits = 15;
		public int AppMinStencilBits = 0;
		public bool AppUsesDepthBuffer = true;
		public bool AppUsesMixedVP = false; // whether app can take advantage of mixed vp mode
		public bool AppRequiresWindowed = false;
		public bool AppRequiresFullscreen = false;


    
    
		/// <summary>
		/// Enumerates available D3D adapters, devices, modes, etc.
		/// </summary>
		public void Enumerate()
		{
			foreach (AdapterInformation ai in Manager.Adapters)
			{
				ArrayList adapterFormatList = new ArrayList();
				GraphicsAdapterInfo adapterInfo = new GraphicsAdapterInfo();
				adapterInfo.AdapterOrdinal = ai.Adapter;
				adapterInfo.AdapterDetails = ai.Information;

				// Get list of all display modes on this adapter.  
				// Also build a temporary list of all display adapter formats.
				foreach (DisplayMode displayMode in ai.SupportedDisplayModes)
				{
					if (displayMode.Width < AppMinFullscreenWidth)
						continue;
					if (displayMode.Height < AppMinFullscreenHeight)
						continue;
					if (GraphicsUtility.GetColorChannelBits(displayMode.Format) < AppMinColorChannelBits)
						continue;
					adapterInfo.DisplayModeList.Add(displayMode);
					if (!adapterFormatList.Contains(displayMode.Format))
						adapterFormatList.Add(displayMode.Format);
				}

				// Sort displaymode list
				DisplayModeComparer dmc = new DisplayModeComparer();
				adapterInfo.DisplayModeList.Sort(dmc);

				// Get info for each device on this adapter
				EnumerateDevices(adapterInfo, adapterFormatList);

				// If at least one device on this adapter is available and compatible
				// with the app, add the adapterInfo to the list
				if (adapterInfo.DeviceInfoList.Count == 0)
					continue;
				AdapterInfoList.Add(adapterInfo);
			}
		}


    
    
		/// <summary>
		/// Enumerates D3D devices for a particular adapter
		/// </summary>
		protected void EnumerateDevices(GraphicsAdapterInfo adapterInfo, ArrayList adapterFormatList)
		{
			DeviceType[] devTypeArray = new DeviceType[] 
			{ DeviceType.Hardware, DeviceType.Software, DeviceType.Reference };

			foreach (DeviceType devType in devTypeArray)
			{
				GraphicsDeviceInfo deviceInfo = new GraphicsDeviceInfo();
				deviceInfo.AdapterOrdinal = adapterInfo.AdapterOrdinal;
				deviceInfo.DevType = devType;
				try
				{
					deviceInfo.Caps = Manager.GetDeviceCaps(adapterInfo.AdapterOrdinal, devType);
				}
				catch (DirectXException)
				{
					continue;
				}

				// Get info for each devicecombo on this device
				EnumerateDeviceCombos(deviceInfo, adapterFormatList);

				// If at least one devicecombo for this device is found, 
				// add the deviceInfo to the list
				if (deviceInfo.DeviceComboList.Count == 0)
					continue;
				adapterInfo.DeviceInfoList.Add(deviceInfo);
			}
		}


    
    
		/// <summary>
		/// Enumerates DeviceCombos for a particular device
		/// </summary>
		protected void EnumerateDeviceCombos(GraphicsDeviceInfo deviceInfo, ArrayList adapterFormatList)
		{
			Format[] backBufferFormatArray = new Format[] 
			{
					Format.A8R8G8B8, Format.X8R8G8B8, Format.A2R10G10B10, 
				Format.R5G6B5, Format.A1R5G5B5, Format.X1R5G5B5,
			};
			bool[] isWindowedArray = new bool[] { false, true };

			// See which adapter formats are supported by this device
			foreach (Format adapterFormat in adapterFormatList)
			{
				foreach (Format backBufferFormat in backBufferFormatArray)
				{
					if (GraphicsUtility.GetAlphaChannelBits(backBufferFormat) < AppMinAlphaChannelBits)
						continue;
					foreach (bool isWindowed in isWindowedArray)
					{
						if (!isWindowed && AppRequiresWindowed)
							continue;
						if (isWindowed && AppRequiresFullscreen)
							continue;
						if (!Manager.CheckDeviceType(deviceInfo.AdapterOrdinal, deviceInfo.DevType, adapterFormat, backBufferFormat, isWindowed))
						{
							continue;
						}

						// At this point, we have an adapter/device/adapterformat/backbufferformat/iswindowed
						// DeviceCombo that is supported by the system.  We still need to confirm that it's 
						// compatible with the app, and find one or more suitable depth/stencil buffer format,
						// multisample type, vertex processing type, and present interval.
						DeviceCombo deviceCombo = new DeviceCombo();
						deviceCombo.AdapterOrdinal = deviceInfo.AdapterOrdinal;
						deviceCombo.DevType = deviceInfo.DevType;
						deviceCombo.AdapterFormat = adapterFormat;
						deviceCombo.BackBufferFormat = backBufferFormat;
						deviceCombo.IsWindowed = isWindowed;
						if (AppUsesDepthBuffer)
						{
							BuildDepthStencilFormatList(deviceCombo);
							if (deviceCombo.DepthStencilFormatList.Count == 0)
								continue;
						}
						BuildMultiSampleTypeList(deviceCombo);
						if (deviceCombo.MultiSampleTypeList.Count == 0)
							continue;
						BuildDepthStencilMultiSampleConflictList(deviceCombo);
						BuildVertexProcessingTypeList(deviceInfo, deviceCombo);
						if (deviceCombo.VertexProcessingTypeList.Count == 0)
							continue;
						BuildPresentIntervalList(deviceInfo, deviceCombo);
						if (deviceCombo.PresentIntervalList.Count == 0)
							continue;

						deviceInfo.DeviceComboList.Add(deviceCombo);
					}
				}
			}
		}


    
    
		/// <summary>
		/// Adds all depth/stencil formats that are compatible with the device and app to
		/// the given deviceCombo
		/// </summary>
		public void BuildDepthStencilFormatList(DeviceCombo deviceCombo)
		{
			DepthFormat[] depthStencilFormatArray = 
		{
			DepthFormat.D16,
			DepthFormat.D15S1,
			DepthFormat.D24X8,
			DepthFormat.D24S8,
			DepthFormat.D24X4S4,
			DepthFormat.D32,
			};

			foreach (DepthFormat depthStencilFmt in depthStencilFormatArray)
			{
				if (GraphicsUtility.GetDepthBits(depthStencilFmt) < AppMinDepthBits)
					continue;
				if (GraphicsUtility.GetStencilBits(depthStencilFmt) < AppMinStencilBits)
					continue;
				if (Manager.CheckDeviceFormat(deviceCombo.AdapterOrdinal, deviceCombo.DevType, deviceCombo.AdapterFormat, 
					Usage.DepthStencil, ResourceType.Surface, depthStencilFmt))
				{
					if (Manager.CheckDepthStencilMatch(deviceCombo.AdapterOrdinal, deviceCombo.DevType,
						deviceCombo.AdapterFormat, deviceCombo.BackBufferFormat, depthStencilFmt))
					{
						deviceCombo.DepthStencilFormatList.Add(depthStencilFmt);
					}
				}
			}
		}


    
    
		/// <summary>
		/// Adds all multisample types that are compatible with the device and app to
		/// the given deviceCombo
		/// </summary>
		public void BuildMultiSampleTypeList(DeviceCombo deviceCombo)
		{
			MultiSampleType[] msTypeArray = { 
												MultiSampleType.None,
												MultiSampleType.NonMaskable,
												MultiSampleType.TwoSamples,
												MultiSampleType.ThreeSamples,
												MultiSampleType.FourSamples,
												MultiSampleType.FiveSamples,
												MultiSampleType.SixSamples,
												MultiSampleType.SevenSamples,
												MultiSampleType.EightSamples,
												MultiSampleType.NineSamples,
												MultiSampleType.TenSamples,
												MultiSampleType.ElevenSamples,
												MultiSampleType.TwelveSamples,
												MultiSampleType.ThirteenSamples,
												MultiSampleType.FourteenSamples,
												MultiSampleType.FifteenSamples,
												MultiSampleType.SixteenSamples,
			};

			foreach (MultiSampleType msType in msTypeArray)
			{
				int result;
				int qualityLevels = 0;
				if (Manager.CheckDeviceMultiSampleType(deviceCombo.AdapterOrdinal, deviceCombo.DevType, 
					deviceCombo.BackBufferFormat, deviceCombo.IsWindowed, msType, out result, out qualityLevels))
				{
					deviceCombo.MultiSampleTypeList.Add(msType);
					deviceCombo.MultiSampleQualityList.Add(qualityLevels);
				}
			}
		}


    
    
		/// <summary>
		/// Finds any depthstencil formats that are incompatible with multisample types and
		/// builds a list of them.
		/// </summary>
		public void BuildDepthStencilMultiSampleConflictList(DeviceCombo deviceCombo)
		{
			DepthStencilMultiSampleConflict DSMSConflict;

			foreach (DepthFormat dsFmt in deviceCombo.DepthStencilFormatList)
			{
				foreach (MultiSampleType msType in deviceCombo.MultiSampleTypeList)
				{
					if (!Manager.CheckDeviceMultiSampleType(deviceCombo.AdapterOrdinal,
						deviceCombo.DevType, (Format)dsFmt, deviceCombo.IsWindowed, msType))
					{
						DSMSConflict = new DepthStencilMultiSampleConflict();
						DSMSConflict.DepthStencilFormat = dsFmt;
						DSMSConflict.MultiSampleType = msType;
						deviceCombo.DepthStencilMultiSampleConflictList.Add(DSMSConflict);
					}
				}
			}
		}


    
    
		/// <summary>
		/// Adds all vertex processing types that are compatible with the device and app to
		/// the given deviceCombo
		/// </summary>
		public void BuildVertexProcessingTypeList(GraphicsDeviceInfo deviceInfo, DeviceCombo deviceCombo)
		{
			if (deviceInfo.Caps.DeviceCaps.SupportsHardwareTransformAndLight)
			{
				if (deviceInfo.Caps.DeviceCaps.SupportsPureDevice)
				{
					if (ConfirmDeviceCallback == null ||
						ConfirmDeviceCallback(deviceInfo.Caps, VertexProcessingType.PureHardware, 
						deviceCombo.AdapterFormat, deviceCombo.BackBufferFormat))
					{
						deviceCombo.VertexProcessingTypeList.Add(VertexProcessingType.PureHardware);
					}
				}
				if (ConfirmDeviceCallback == null ||
					ConfirmDeviceCallback(deviceInfo.Caps, VertexProcessingType.Hardware, 
					deviceCombo.AdapterFormat, deviceCombo.BackBufferFormat))
				{
					deviceCombo.VertexProcessingTypeList.Add(VertexProcessingType.Hardware);
				}
				if (AppUsesMixedVP && (ConfirmDeviceCallback == null ||
					ConfirmDeviceCallback(deviceInfo.Caps, VertexProcessingType.Mixed, 
					deviceCombo.AdapterFormat, deviceCombo.BackBufferFormat)))
				{
					deviceCombo.VertexProcessingTypeList.Add(VertexProcessingType.Mixed);
				}
			}
			if (ConfirmDeviceCallback == null ||
				ConfirmDeviceCallback(deviceInfo.Caps, VertexProcessingType.Software, 
				deviceCombo.AdapterFormat, deviceCombo.BackBufferFormat))
			{
				deviceCombo.VertexProcessingTypeList.Add(VertexProcessingType.Software);
			}
		}


    
    
		/// <summary>
		/// Adds all present intervals that are compatible with the device and app to
		/// the given deviceCombo
		/// </summary>
		public void BuildPresentIntervalList(GraphicsDeviceInfo deviceInfo, DeviceCombo deviceCombo)
		{
			PresentInterval[] piArray = { 
											PresentInterval.Immediate,
											PresentInterval.Default,
											PresentInterval.One,
											PresentInterval.Two,
											PresentInterval.Three,
											PresentInterval.Four,
			};

			foreach (PresentInterval pi in piArray)
			{
				if (deviceCombo.IsWindowed)
				{
					if (pi == PresentInterval.Two ||
						pi == PresentInterval.Three ||
						pi == PresentInterval.Four)
					{
						// These intervals are not supported in windowed mode.
						continue;
					}
				}
				// Note that PresentInterval.Default is zero, so you
				// can't do a caps check for it -- it is always available.
				if (pi == PresentInterval.Default ||
					(deviceInfo.Caps.PresentationIntervals & pi) != (PresentInterval)0)
				{
					deviceCombo.PresentIntervalList.Add(pi);
				}
			}
		}
	}
}